﻿using System;
using System.Collections;
using System.Web;
using System.Web.Caching;
using SBCache;

namespace SBCache.Web
{
    /// <summary>
    /// Web cache store.
    /// </summary>
    public class WebCache : ICache
    {
        /// <summary>
        /// Tag prefix for the cache dependency key.
        /// </summary>
        private const string TagPrefix = "tag:";

        /// <summary>
        /// ASP.NET cache.
        /// </summary>
        private readonly Cache cache = HttpContext.Current.Cache;

        /// <summary>
        /// Puts an item into the cache store with a cache key to reference its location.
        /// </summary>
        /// <param name="key">The cache key used to reference the item.</param>
        /// <param name="item">The item to be inserted into the cache.</param>
        public void Put(string key, object item)
        {
            Put(key, item, null, Cache.NoSlidingExpiration, Cache.NoAbsoluteExpiration);
        }

        /// <summary>
        /// Puts an item into the cache store with a cache key to reference its location.
        /// </summary>
        /// <param name="key">The cache key used to reference the item.</param>
        /// <param name="item">The item to be inserted into the cache.</param>
        /// <param name="absoluteExpiration">Cache absolute expiration.</param>
        public void Put(string key, object item, DateTime absoluteExpiration)
        {
            Put(key, item, null, Cache.NoSlidingExpiration, absoluteExpiration);
        }

        /// <summary>
        /// Puts an item into the cache store with a cache key to reference its location.
        /// </summary>
        /// <param name="key">The cache key used to reference the item.</param>
        /// <param name="item">The item to be inserted into the cache.</param>
        /// <param name="cacheDependency">Cache dependency.</param>
        /// <param name="slidingExpiration">Cache sliding expiration.</param>
        public void Put(string key, object item, string cacheDependency, TimeSpan slidingExpiration)
        {
            Put(key, item, cacheDependency, slidingExpiration, Cache.NoAbsoluteExpiration);
        }

        /// <summary>
        /// Puts an item into the cache store with a cache key to reference its location.
        /// </summary>
        /// <param name="key">The cache key used to reference the item.</param>
        /// <param name="item">The item to be inserted into the cache.</param>
        /// <param name="cacheDependency">Cache dependency.</param>
        /// <param name="slidingExpiration">Cache sliding expiration.</param>
        /// <param name="absoluteExpiration">Cache absolute expiration.</param>
        public void Put(string key, object item, string cacheDependency, TimeSpan slidingExpiration, DateTime absoluteExpiration)
        {
            CacheDependency dependency = null;
            if (!String.IsNullOrEmpty(cacheDependency))
            {
                EnsureDependencyExists(GetDependencyKey(cacheDependency));

                dependency = new CacheDependency(new string[0], new[] { GetDependencyKey(cacheDependency) });
            }

            try
            {
                cache.Insert(key, item, dependency, absoluteExpiration, slidingExpiration);
            }
            catch (Exception)
            {
                // there's a possibility that one of the dependencies has been evicted by another thread
                // in this case just don't put this item in the cache
            }
        }

        /// <summary>
        /// Gets the specified item from the cache store.
        /// </summary>
        /// <param name="key">The cache key.</param>
        /// <returns>The retrieved cache item, or null if the key is not found.</returns>
        public object Get(string key)
        {
            return cache.Get(key);
        }

        /// <summary>
        /// Invalidates the specified item with the provided key.
        /// </summary>
        /// <param name="key">The cache key.</param>
        public void Invalidate(string key)
        {
            cache.Remove(key);
            cache.Remove(GetDependencyKey(key));
        }

        /// <summary>
        /// Clears the cache store.
        /// </summary>
        public void Clear()
        {
            IDictionaryEnumerator enumerator = cache.GetEnumerator();

            while (enumerator.MoveNext())
            {
                cache.Remove(enumerator.Key.ToString());
            }
        }

        /// <summary>
        /// Gets the dependency key.
        /// </summary>
        /// <param name="key">Cache key.</param>
        /// <returns>The cache dependency key.</returns>
        private string GetDependencyKey(string key)
        {
            return TagPrefix + key;
        }

        /// <summary>
        /// Ensures the dependency key exists.
        /// </summary>
        /// <param name="key">The cache dependency key.</param>
        private void EnsureDependencyExists(string key)
        {
            if (cache.Get(key) == null)
            {
                try
                {
                    cache.Insert(key, key, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);
                }
                catch (Exception)
                {
                    // ignore exceptions.
                }
            }
        }
    }
}
